#include "VulkanApplication.h"
#include <vector>
#include <iostream>

VulkanApplication::VulkanApplication(const char* appName, int w, int h)
    : _instance(nullptr), _physicalDevice(nullptr), _logicDevice(nullptr),
      _surface(nullptr), _window(nullptr)
{
    if (!glfwInit())
    {
        printf("Failed to initialize GLFW.\n");
        _initialized = false; return;
    }

    if (!glfwVulkanSupported())
    {
        printf("Vulkan not supported.\n");
        _initialized = false; return;
    }

    _initialized = createInstance(appName);
    if (_initialized) _initialized = obtainPhysicalDevice();
    if (_initialized) _initialized = createLogicDevice();
    if (_initialized) _initialized = createWindowSurface(appName, w, h);
}

VulkanApplication::~VulkanApplication()
{
    if (_surface != nullptr) vkDestroySurfaceKHR(_instance, _surface, nullptr);
    if (_window != nullptr) glfwDestroyWindow(_window);
    if (_logicDevice != nullptr) vkDestroyDevice(_logicDevice, nullptr);
    if (_instance != nullptr) vkDestroyInstance(_instance, nullptr);
    glfwTerminate();
}

void VulkanApplication::mouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
    printf("MOUSEBUTTON: %d, %d\n", button, action);
}

void VulkanApplication::keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    printf("KEY: %d, %d\n", key, action);
    if (key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, 1);
}

void VulkanApplication::resizeCallback(GLFWwindow* window, int width, int height)
{
    printf("Resized: %d x %d\n", width, height);
}

bool VulkanApplication::createInstance(const char* appName)
{
    uint32_t glfwExtCount = 0;
    const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtCount);
    printf("GLFW initialized. It requires the following extensions:\n");
    for (uint32_t i = 0; i < glfwExtCount; i++)
        printf("    %s\n", glfwExtensions[i]);
    
    const char* layerNames[] =
    {
        "VK_LAYER_KHRONOS_validation"
    };

    VkApplicationInfo appInfo
    {
        VK_STRUCTURE_TYPE_APPLICATION_INFO,
        nullptr,
        appName,
        VK_MAKE_VERSION(1, 0, 0),
        "VulkanClass",
        VK_MAKE_VERSION(1, 0, 0),
        VK_API_VERSION_1_3,
    };

    VkInstanceCreateInfo instanceInfo
    {
        VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        nullptr, 0,
        &appInfo,
        1, layerNames,
        glfwExtCount, glfwExtensions
    };

    VkResult result = vkCreateInstance(&instanceInfo, nullptr, &_instance);
    if (result != VK_SUCCESS)
    {
        printf("Vulkan instance creation failed: %d\n", result);
        return false;
    }
    return true;
}

bool VulkanApplication::obtainPhysicalDevice()
{
    uint32_t deviceCount = 0;
    VkResult result = vkEnumeratePhysicalDevices(_instance, &deviceCount, nullptr);
    if (result != VK_SUCCESS || deviceCount == 0)
    {
        printf("Failed to enumerate physical devices.\n");
        return false;
    }

    std::vector<VkPhysicalDevice> devices(deviceCount);
    vkEnumeratePhysicalDevices(_instance, &deviceCount, devices.data());
    printf("vkEnumeratePhysicalDevices() has found some physical devices:\n");
    for (uint32_t i = 0; i < deviceCount; ++i)
    {
        VkPhysicalDeviceProperties props;
        vkGetPhysicalDeviceProperties(devices[i], &props);

        if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
            printf("    %d: Discrete GPU, %s\n", i, props.deviceName);
        else if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
            printf("    %d: Integrated GPU, %s\n", i, props.deviceName);
        else
            printf("    %d: Other GPU, %s\n", i, props.deviceName);
    }

    _physicalDevice = devices[0];
    return true;
}

bool VulkanApplication::createLogicDevice()
{
    uint32_t queueFamilyCount = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(_physicalDevice, &queueFamilyCount, nullptr);
    if (queueFamilyCount == 0)
    {
        printf("Failed to get queue family data.\n");
        return false;
    }

    std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(_physicalDevice, &queueFamilyCount, queueFamilies.data());

    VkPhysicalDeviceFeatures features;
    vkGetPhysicalDeviceFeatures(_physicalDevice, &features);

    printf("Current physical device supports:\n");
    printf("    Graphics = %d\n", (queueFamilies[0].queueFlags & VK_QUEUE_GRAPHICS_BIT) > 0);
    printf("    Compute = %d\n", (queueFamilies[0].queueFlags & VK_QUEUE_COMPUTE_BIT) > 0);
    printf("    Geometry = %d\n", features.geometryShader);
    printf("    Tessellation = %d\n", features.tessellationShader);

    char* deviceExtensions[] =
    {
        "VK_KHR_swapchain"
    };
    
    float queuePriorities = 1.0f;
    VkDeviceQueueCreateInfo queueCreateInfo
    {
        VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
        nullptr, 0,
        0, 1,  // queueFamilyIndex, queueCount
        &queuePriorities
    };

    VkDeviceCreateInfo createInfo
    {
        VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        nullptr, 0,
        1, &queueCreateInfo,
        0, nullptr,
        1, deviceExtensions,
        &features
    };

    VkResult result = vkCreateDevice(_physicalDevice, &createInfo, nullptr, &_logicDevice);
    if (result != VK_SUCCESS)
    {
        printf("Failed to create logic device.\n");
        return false;
    }
    return true;
}

bool VulkanApplication::createWindowSurface(const char* appName, int w, int h)
{
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    _window = glfwCreateWindow(w, h, appName, nullptr, nullptr);
    if (!_window)
    {
        printf("Failed to create GLFW window.\n");
        return false;
    }

    VkResult result = glfwCreateWindowSurface(_instance, _window, nullptr, &_surface);
    if (result != VK_SUCCESS)
    {
        printf("Failed to create window surface.\n");
        return false;
    }

    glfwSetWindowUserPointer(_window, this);
    glfwSetMouseButtonCallback(_window, [](GLFWwindow* w, int button, int action, int mods)
    {
        VulkanApplication* app = static_cast<VulkanApplication*>(glfwGetWindowUserPointer(w));
        app->mouseButtonCallback(w, button, action, mods);
    });
    glfwSetKeyCallback(_window, [](GLFWwindow* w, int key, int scancode, int action, int mods)
    {
        VulkanApplication* app = static_cast<VulkanApplication*>(glfwGetWindowUserPointer(w));
        app->keyCallback(w, key, scancode, action, mods);
    });
    glfwSetWindowSizeCallback(_window, [](GLFWwindow* w, int width, int height)
    {
        VulkanApplication* app = static_cast<VulkanApplication*>(glfwGetWindowUserPointer(w));
        app->resizeCallback(w, width, height);
    });
    return true;
}
